#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/device.h>
#include <linux/cdev.h>

#define HELLO_DBG
#ifdef HELLO_DBG
#define hello_dbg(fmt, ...) printk("[%s][%d]" fmt "\r\n", __func__, __LINE__, ##__VA_ARGS__)
#else
#define hello_dbg(fmt, ...)
#endif

#define CLASS_NAME "hello"
#define DEV_NAME "hello"
#define HELLO_MAJOR (200)

static dev_t devt;
static struct cdev *pcdev = NULL;
static struct class *hello_cls = NULL;
static struct device *hello_dev = NULL;

static ssize_t hello_read(struct file *file, char __user *user, size_t len, loff_t *loff)
{
    char buf[32] = "hello read";
    unsigned long ret;

    hello_dbg("");

    if (len >= strlen(buf))
        len = strlen(buf);

    ret = copy_to_user(user, buf, len);
    if (ret == 0)
    {
        return len;
    }
    else
    {
        hello_dbg("copy_to_user err, %lu", ret);
        return ret;
    }

    return 0;
}

static ssize_t hello_write(struct file *file, const char __user *user, size_t len, loff_t *loff)
{
    char buf[32] = {0};
    unsigned long ret;

    hello_dbg("");

    if (len >= 32)
        return -EMSGSIZE;

    ret = copy_from_user(buf, user, len);
    if (ret == 0)
    {
        hello_dbg("write ok, %d, %s", len, buf);
        return len;
    }
    else
    {
        hello_dbg("write err, %lu", ret);
        return ret;
    }

    return 0;
}

static int hello_open(struct inode *inode, struct file *file)
{
    hello_dbg("");

    return 0;
}
static int hello_release(struct inode *inode, struct file *file)
{
    hello_dbg("");

    return 0;
}

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = hello_read,
    .write = hello_write,
    .open = hello_open,
    .release = hello_release,
};

static int __init hello_class_init(void)
{
    int ret;

    hello_dbg("");

    ret = alloc_chrdev_region(&devt, 0, 2, DEV_NAME);
    if (ret)
    {
        hello_dbg("register_chrdev_region error:%d", ret);
        goto err0;
    }

    pcdev = cdev_alloc();
    if (pcdev == NULL)
    {
        hello_dbg("cdev_alloc error");
        ret = -ENOMEM;
        goto err1;
    }

    cdev_init(pcdev, &fops);

    ret = cdev_add(pcdev, devt, 2);
    if (ret)
    {
        hello_dbg("cdev_add error:%d", ret);
        goto err2;
    }

    hello_cls = class_create(THIS_MODULE, CLASS_NAME);
    if (IS_ERR(hello_cls))
    {
        hello_dbg("class_create error");
        ret = PTR_ERR(hello_cls);
        goto err2;
    }

    hello_dev = device_create(hello_cls, NULL, devt, NULL, DEV_NAME);
    if (IS_ERR(hello_cls))
    {
        hello_dbg("device_create error");
        ret = PTR_ERR(hello_dev);
        goto err3;
    }

    return 0;

err3:
    class_destroy(hello_cls);
err2:
    cdev_del(pcdev);
    if (pcdev)
    {
        kfree(pcdev);
        pcdev = NULL;
    }
err1:
    unregister_chrdev_region(devt, 2);
err0:
    return ret;
}

static void __exit hello_class_exit(void)
{
    device_destroy(hello_cls, devt);
    class_destroy(hello_cls);
    cdev_del(pcdev);
    if (pcdev)
    {
        kfree(pcdev);
        pcdev = NULL;
    }
    unregister_chrdev_region(devt, 2);

    hello_dbg("");
}

module_init(hello_class_init);

module_exit(hello_class_exit);

MODULE_AUTHOR("ljq");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("test class driver");
